package com.hk.core.jdbc.query;

import com.hk.commons.query.MatchMode;
import com.hk.commons.query.Operator;
import com.hk.commons.util.ObjectUtils;
import com.hk.commons.util.StringUtils;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.*;

/**
 * @author kevin
 * @date 2018-09-19 11:00
 */
@Getter
@Setter
@NoArgsConstructor
public class SimpleCondition implements Condition {

    private String field;

    private Operator operator;

    private Object value;

    private static Map<Operator, ComparableCondition> conditions;

    static {
        conditions = new EnumMap<>(Operator.class);
        var c = binaryCondition();
        conditions.put(Operator.EQ, c);
        conditions.put(Operator.LT, c);
        conditions.put(Operator.LTE, c);
        conditions.put(Operator.GT, c);
        conditions.put(Operator.GTE, c);

        c = inCondition();
        conditions.put(Operator.IN, c);
        conditions.put(Operator.NOT_IN, c);

        conditions.put(Operator.NE, notEqCondition());
        conditions.put(Operator.IS_NULL, (field, operator, compareValue, parametric, parameters) -> String.format("(%s IS NULL)", field));
        conditions.put(Operator.IS_NULL_OR_EMPTY, (field, operator, compareValue, parametric, parameters) -> String.format("(%1$s IS NULL OR %1$s = '')", field));
        conditions.put(Operator.IS_NOT_NULL, (field, operator, compareValue, parametric, parameters) -> String.format("(%s IS NOT NULL)", field));
        conditions.put(Operator.LIKE, (BaseLikeCondition) () -> MatchMode.EXACT);
        conditions.put(Operator.LIKE_START, (BaseLikeCondition) () -> MatchMode.START);
        conditions.put(Operator.LIKE_END, (BaseLikeCondition) () -> MatchMode.END);
        conditions.put(Operator.LIKE_ANYWHERE, (BaseLikeCondition) () -> MatchMode.ANYWHERE);

        conditions.put(Operator.BETWEEN, betweenCondition());
    }

    /**
     * @param field        field
     * @param compareValue compareValue
     */
    public SimpleCondition(String field, Object compareValue) {
        this(field, Operator.EQ, compareValue);
    }

    /**
     * @param field    field
     * @param operator operator
     * @param value    value
     */
    public SimpleCondition(String field, Operator operator, Object value) {
        this.field = field;
        this.operator = operator;
        this.value = value;
    }

    @Override
    public String toSqlString(Parametric parametric, List<Object> parameters) {
        var c = getSubCondition(operator);
        return null != c ? c.toSqlString(field, operator, value, parametric, parameters) : null;
    }

    private ComparableCondition getSubCondition(Operator operator) {
        if (null == operator) {
            operator = Operator.EQ;
        }
        return conditions.get(operator);
    }

    @FunctionalInterface
    private interface ComparableCondition {

        String toSqlString(String field, Operator operator, Object compareValue, Parametric parametric, List<Object> parameters);
    }

    private static ComparableCondition betweenCondition() {
        return (field, operator, compareValue, parametric, parameters) -> {
            if (Objects.isNull(compareValue) || !(compareValue instanceof Object[] value)) {
                return null;
            }
            if (value.length != 2) {
                return null;
            }
            parameters.add(value[0]);
            parameters.add(value[1]);
            return String.format("(%s BETWEEN %s AND %s)", parametric.parametric(field, parameters.size() - 1),
                    field, parametric.parametric(field, parameters.size()));
        };

    }

    private static ComparableCondition inCondition() {
        return (field, operator, compareValue, parametric, parameters) -> {
            Iterable<?> iterable = null;
            if (compareValue instanceof Object[] array) {
                iterable = Arrays.asList(array);
            } else if (compareValue instanceof Iterable<?> iter) {
                iterable = iter;
            }
            if (Objects.isNull(iterable)) {
                return null;
            }
            var sb = new StringBuilder();
            int index = 0;
            for (var value : iterable) {
                if (index++ > 0) {
                    sb.append(",");
                }
                parameters.add(value);
                sb.append(parametric.parametric(field, parameters.size()));
            }
            if (index == 1) { //只有一个元素,使用 (xx = ?)
                return String.format("(%s %s %s)", field, operator == Operator.IN ? "=" : "<>", parametric.parametric(field, parameters.size()));
            }
            var inWhich = sb.toString();
            if (inWhich.isEmpty()) {
                // 空集合
                if (operator == Operator.IN) {
                    // xx in () 会报错；in一个空集合的结果应该是空，所以加一个1=2的条件
                    return "(1 = 2)";
                } else {
                    // not in 空集合，返回null，表示该条件不起作用。
                    return null;
                }
            }
            return String.format("(%s %s (%s))", field, operator == Operator.IN ? "IN" : "NOT IN", inWhich);
        };
    }

    @FunctionalInterface
    private interface BaseLikeCondition extends ComparableCondition {

        MatchMode getLikeMatchMode();

        @Override
        default String toSqlString(String field, Operator operator, Object compareValue, Parametric parametric, List<Object> parameters) {
            if (Objects.isNull(compareValue)) {
                return null;
            }
            var value = ObjectUtils.toString(compareValue);
            if (StringUtils.isEmpty(value)) {
                return null;
            }
            parameters.add(getLikeMatchMode().toMatchString(value));
            return String.format("(%s LIKE %s)", field, parametric.parametric(field, parameters.size()));
        }
    }

    private static ComparableCondition binaryCondition() {
        return (field, operator, compareValue, parametric, parameters) -> {
            if (org.springframework.util.ObjectUtils.isEmpty(compareValue)) {
                return null;
            }
            var sqlCompare = switch (operator) {
                case EQ:
                    yield "=";
                case LT:
                    yield "<";
                case LTE:
                    yield "<=";
                case GT:
                    yield ">";
                case GTE:
                    yield ">=";
                default:
                    yield null;
            };
            if (Objects.isNull(sqlCompare)) {
                return null;
            }
            parameters.add(compareValue);
            return String.format("(%s %s %s)", field, sqlCompare, parametric.parametric(field, parameters.size()));
        };
    }

    private static ComparableCondition notEqCondition() {
        return (field, operator, compareValue, parametric, parameters) -> {
            parameters.add(compareValue);
            return String.format("(%1$s <> %2$s)", field, parametric.parametric(field, parameters.size()));
        };
    }
}
